Video Thumbnail
2:14
2:09
clock icon Created with Sketch. 2 minutes

Solution: Unit Tests (Advanced)

Although in the video I’m using Pydantic V1, we’ve updated the code example to Pydantic V2. As a result, there might be minor differences between the code you see in the video and the code in the Git repository. Notably, .dict() has been replaced by .model_dump().


Philipp Walter

Hi Arjan and Team,

running following test failed, because the response contains an empty json.

def test_book_ticket(test_db) -> None:

event_data_1 = {
"title": "Test event 1",
"location": "Test location 1",
"start_date": "2023-09-22 12:00:00",
"end_date": "2023-09-22 14:00:00",
"available_tickets": 5,
}

client.post("/events", json=event_data_1)

response = client.get("/events")

time.sleep(1)

ticket_event_1 = TicketCreate(event_id=1, customer_name="Test customer", customer_email="test_customer@internet.com")

response_first_booking = client.post("/tickets", json=ticket_event_1.model_dump())
assert response_first_booking.status_code == 200
assert response_first_booking.json()["event_id"] == 1
assert response_first_booking.json()["customer_name"] == "Test customer"
assert response_first_booking.json()["customer_email"] == "test_customer@internet.com"

client.post("/tickets", json=ticket_event_1.model_dump())

response = client.get("/events/1")

assert response.json()["available_tickets"] == 3

I could solve this by changing the according book_ticket api from:

db_ticket = Ticket(**ticket.model_dump())
database.add(db_ticket)
database.commit()
database.refresh(db_ticket)

event.available_tickets -= 1
database.commit()

to

event.available_tickets -= 1
database.commit()

db_ticket = Ticket(**ticket.model_dump())
database.add(db_ticket)
database.commit()
database.refresh(db_ticket)

But I do not fully understand why :-(
The Test would also pass with the first version, when I run it step by step in the debug-mode!

REPLY
Arjan Egges

Hi Philip, I'm not 100% sure what could cause this. When I created this challenge, I do remember that I encountered something similar. This had to do with the testing framework deleting and re-creating the client between tests. As a result, the data that was in the in-memory database was cleared and so some tests failed. Perhaps something like that is going on here?

REPLY
Philipp Walter

Hi Arjan,

for me it looks very reproducable and the failure only occures using pytest (even with only one test) and when the database.refresh(db_ticket) is not the last database-interaction in the API.

Looks like the commit and refresh methods behave different running the database in-memory.
Because calling the API via request in a "normal" script is indepentend of the order calling those methods.

REPLY
Arjan Egges

Hi Philipp, that's really strange. When we have some time to make improvements to the challenges and solutions, we'll look into a better way of running the tests that is more stable. If you have a specific suggestion, do let me know!

REPLY
Rafal Sukiennik

```assert response.json().get("detail") == "No available tickets"``` passes

but cannot get this working:
```with pytest.raises(HTTPException):
client.post("/tickets", json=ticket_payload)```

E Failed: DID NOT RAISE ...

REPLY
Arjan Egges

Hi Rafal, not sure what the issue is - that looks like it should work. Can you post the complete test including the test data that should raise the error?

REPLY
Rafal Sukiennik

Hi Arjan. Here is the full looong booking story :).

def test_book_ticket() -> None:

events = client.get("/events").json()
for event in events:
client.delete(f"/events/{event.get('id')}")

payload = {
"title": "Advanced Testing Fun",
"location": "Design Challenge",
"start_date": "2023-09-22 12:00:00",
"end_date": "2023-09-22 14:00:00",
"available_tickets": 20,
}
client.post("/events", json=payload)
event = client.get("/events").json()[0]
available_tickets = event.get("available_tickets")

ticket = {
"event_id": event["id"],
"customer_name": fake.name(),
"customer_email": fake.email()
}

# booking all available tickets
for _ in range(1, available_tickets + 1):
client.post("/tickets", json=ticket)

response = client.post("/tickets", json=ticket)

assert response.status_code == 400 # passes
assert response.json().get("detail") == "No available tickets" # passes

with pytest.raises(HTTPException):
client.post("/tickets", json=ticket) # fails with DID NOT RAISE

REPLY
Arjan Egges

Hi Rafal, now that I think about it, it's possible that the issue is that the post request doesn't raise an HTTPException since that is something that happens in the backend itself. The post method translates the HTTPException into a 400 response. So this would actually be the expected behavior.

REPLY
Oscar Bell

Although the test cases in your example all pass, be aware that when you add for instance another one of the test cases created in the previous challenges some of the test might fail.

For instance if you add the test_create_event just before the test_no_available_tickets the database already contains an event with id 1 which has 50 tickets available and thus the test test_no_available_tickets while fail because it is referring to another event than the one in the test case provided. As long as the test suite is running the database created in memory keeps the records that where created in previous test cases and thus the starting point for a test case might be different.

To resolve this, some kind of setup and teardown is needed per test case, to assure the starting point for the test case is setup correctly.

Within 1 test file you can control the setup of your test cases but when adding a 2nd test file that uses the same logic as in the 1st test file, you might get unexpected results.
To resolve this issue create a helper functions that defines the override_get_db function and its dependency.
In every test file this override_get_db function must be imported so that all test cases are referring to the same database and can see the updates which where created.

REPLY
Arjan Egges

Good points, Oscar!

REPLY